﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;

using LExperiment.NetworkLib;

namespace LExperiment.Daemon
{
    internal sealed class LMasterChannel : IDisposable
    {
        // ================================================================== //
        #region [ member variables ]

        private const string SubProcessName = "LSubscriber.exe";

        private LTcpChannel m_channel;
        private bool m_disposed;
        private IDictionary<int, Process> m_subscriberProcs;

        #endregion

        // ================================================================== //
        #region [ constructor and destructor ]

        public LMasterChannel(Socket masterSocket)
        {
            m_channel = new LTcpChannel(masterSocket);
            m_channel.BodyRecvedEvtHandler += this.OnBodyReceivedCallback;
            m_channel.PassiveSockClosedEvtHandler += this.OnPassiveClosedCallback;

            m_subscriberProcs = new Dictionary<int, Process>();
        }

        ~LMasterChannel()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (m_disposed)
                return;

            try
            {
                if (isDisposing)
                {
                    m_channel.Dispose();

                    foreach (KeyValuePair<int, Process> kv in m_subscriberProcs)
                    {
                        kv.Value.Kill();// The Kill method executes asynchronously
                    }
                }
            }
            finally
            {
                m_disposed = true;
            }
        }

        #endregion

        // ================================================================== //
        #region [ IO operation ]

        public void BeginRead()
        {
            m_channel.BeginReceive();
        }

        private void OnBodyReceivedCallback(object source, BodyRecvEventArgs evtargs)
        {
            PacketHeader header = evtargs.Header;
            switch (header.Cmd)
            {
                case CmdConstant.Start:
                    OnStartCommand(evtargs.Body);
                    break;

                case CmdConstant.Stop:
                    OnStopCommand(evtargs.Body);
                    break;

                default:
                    throw new ArgumentOutOfRangeException(string.Format("unknown command: {0}", header.Cmd.ToString()));
            }
        }

        private void OnPassiveClosedCallback(object source, EventArgs evtargs)
        {
            // TODO: the strategy to use when the connection with the master is closed still remains more consideration
            // actually no need to kill the subscribers
            this.Dispose();
            Console.WriteLine("[!] connection with the master has been passively closed, all subscribers killed");
        }

        #endregion

        // ================================================================== //
        public void OnDaemonDisposed(object source, EventArgs evtargs)
        {
            Dispose();
            Console.WriteLine("[!] subscriber daemon exits, all subscribers killed");
        }

        // ================================================================== //
        #region [ dealing with command ]

        private void OnStartCommand(byte[] body)
        {
            StartCommand cmd = new StartCommand();
            cmd.Initialize(body, 0);

            string arguments = string.Format("{0} {1}", cmd.PubAddress, cmd.PubPort.ToString());

            int index = 0;
            foreach (int subscriberId in cmd.SubscriberIds)
            {
                if (!m_subscriberProcs.ContainsKey(subscriberId))
                {
                    Process proc = Process.Start(SubProcessName, arguments);
                    if (proc == null)
                        throw new InvalidOperationException("subscriber cannot be launched !!");
                    m_subscriberProcs.Add(subscriberId, proc);

                    ++index;
                    Console.WriteLine("<+{0}> subscriber[{1}] started", index.ToString(), subscriberId.ToString());
                }
            }
        }

        private void OnStopCommand(byte[] body)
        {
            StopCommand cmd = new StopCommand();
            cmd.Initialize(body, 0);

            int index = 0;
            foreach (int subscriberId in cmd.SubscriberIds)
            {
                if (m_subscriberProcs.ContainsKey(subscriberId))
                {
                    m_subscriberProcs[subscriberId].Kill();
                    m_subscriberProcs.Remove(subscriberId);

                    ++index;
                    Console.WriteLine("<-{0}> subscriber[{1}] stopped", index.ToString(), subscriberId.ToString());
                }
            }
        }

        #endregion
    }

    internal sealed class LSubscribeDaemon : IDisposable
    {
        // ================================================================== //
        #region [ member variables ]

        public event EventHandler DisposedEventHandler;

        private Socket m_lsnSocket;
        private bool m_disposed;

        #endregion

        // ================================================================== //
        #region [ constructor and destructor ]

        public LSubscribeDaemon()
        {
            m_lsnSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        ~LSubscribeDaemon()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (m_disposed)
                return;

            try
            {
                if (isDisposing)
                {
                    if (m_lsnSocket != null)
                        m_lsnSocket.Close();

                    if (DisposedEventHandler != null)
                        DisposedEventHandler(this, EventArgs.Empty);
                }
            }
            finally
            {
                m_disposed = true;
            }
        }

        #endregion

        // ================================================================== //
        #region [ accept operation ]

        public void Run(int port)
        {
            IPEndPoint lsnEndpoint = new IPEndPoint(IPAddress.Any, port);
            m_lsnSocket.Bind(lsnEndpoint);
            m_lsnSocket.Listen(10);

            BeginAccept();
        }

        private void BeginAccept()
        {
            m_lsnSocket.BeginAccept(this.OnAcceptedCallback, null);
        }

        private void OnAcceptedCallback(IAsyncResult asyncResult)
        {
            Socket masterSocket = m_lsnSocket.EndAccept(asyncResult);
            LMasterChannel masterChannel = new LMasterChannel(masterSocket);
            this.DisposedEventHandler += masterChannel.OnDaemonDisposed;

            masterChannel.BeginRead();

            // TODO: perform some limitation that only one master can be served
            BeginAccept();
        }

        #endregion
    }
}